package icu.zhhll.linear;

import org.apache.commons.math3.stat.regression.RegressionResults;
import org.apache.commons.math3.stat.regression.SimpleRegression;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 线性关系工具
 *
 * @author zh
 * @date 2023/9/12 17:05
 */
public class Linears {

    private static final Logger LOGGER = LoggerFactory.getLogger(Linears.class);

    private final SimpleRegression regression;

    private final double[][] data;

    private volatile RegressionResults results;

    public Linears(double[][] data) {
        if (data.length < 3) {
            throw new RuntimeException("数据集样本数不能小于3");
        }
        this.data = data;
        // 默认使用的是最小二乘法  y = intercept + slope * x
        regression = new SimpleRegression();
        regression.addData(data); // 数据集
    }

    /**
     * 是否有线性关系
     *
     * @return
     */
    public boolean isLinear() {
        if (isFit()) {
            return isLinear(results);
        }
        return false;

    }

    /**
     * 是否有线性关系
     *
     * @param result
     * @return
     */
    private boolean isLinear(RegressionResults result) {
        double r2 = result.getRSquared();
        return r2 >= 0.8;
    }

    /**
     * 线性表达式
     *
     * @return
     */
    public String linerExp() {
        if (!isLinear()) {
            throw new RuntimeException("没有线性关系");
        }
        double b = regression.getIntercept();
        double k = regression.getSlope();

        return "f(x) =" +
                (b >= 0 ? " " : " - ") +
                Math.abs(b) +
                (k > 0 ? " + " : " - ") +
                Math.abs(k) +
                "x";

    }

    /**
     * 是否计算过
     *
     * @return
     */
    private synchronized boolean isFit() {
        if (results == null)
            fit();
        return true;
    }

    /**
     * 进行计算
     */
    private void fit() {
        results = regression.regress();
    }

    /**
     * 线性关系
     * f(x)=kx+b
     * map中分别存储 k和b，可通过map.get(k)和map.get(b)来进行
     *
     * @return
     */
    public Map<String, BigDecimal> linearFit() {

        if (isFit()) {
            return resultToMap(regression, results);
        }
        return null;

    }

    /**
     * 找到偏离点
     *
     * @param threshold 偏离阈值 如果为0.1，则会找出该点距离该线性关系大于1.1或小于0.9的点
     * @return
     */
    public double[][] deviatePoint(double threshold) {

        if (isFit()) {
            List<double[]> fitData = new ArrayList<>();
            Map<Integer, double[]> fitMap = new HashMap<>();
            double b = regression.getIntercept();
            double k = regression.getSlope();
            double r2 = results.getRSquared();
            //重新计算生成拟合曲线
            int index = 0;
            for (double[] datem : data) {
                double[] xy = {datem[1], k * datem[0] + b};
                fitMap.put(index++, xy);
//                fitData.add(xy);
            }

            List<Integer> indexList = new ArrayList<>();

            for (Map.Entry<Integer, double[]> entry : fitMap.entrySet()) {

                double[] value = entry.getValue();
                // 原始值
                BigDecimal origin = BigDecimal.valueOf(value[0]);
                // 推测值
                BigDecimal infer = BigDecimal.valueOf(value[1]);

                if (infer.compareTo(origin.multiply(BigDecimal.valueOf(1 + threshold))) > 0
                        || infer.compareTo(origin.multiply(BigDecimal.valueOf(1 - threshold))) < 0) {
                    indexList.add(entry.getKey());
                }
            }

            if (indexList.size() > 0) {
                double[][] deviatePoints = new double[indexList.size()][2];
                for (int i = 0; i < indexList.size(); i++) {
                    double[] clone = data[indexList.get(i)].clone();
                    deviatePoints[i] = clone;
                }

                return deviatePoints;
            }


        }
        return null;
    }

    /**
     * 去除异常点后的关系
     *
     * @param threshold
     * @return
     */
    public Map<String, BigDecimal> offDeviatePoint(double threshold) {
        double[][] points = deviatePoint(threshold);
        if (points == null) {
            return linearFit();
        }

        SimpleRegression offRegression = new SimpleRegression();
        offRegression.addData(data);
        offRegression.removeData(points);
        RegressionResults result = offRegression.regress();

        if (isLinear(result)) {
            return resultToMap(offRegression, result);
        }
        throw new RuntimeException("没有线性关系");


    }

    private Map<String, BigDecimal> resultToMap(SimpleRegression regression,
                                                RegressionResults result
    ) {
        Map<String, BigDecimal> expMap = new HashMap<>();
        // f(x)=kx+b


        /*
         * RegressionResults 中是拟合的结果
         * 其中重要的几个参数如下：
         *   parameters:
         *      0: b
         *      1: k
         *   globalFitInfo
         *      0: 平方误差之和, SSE
         *      1: 平方和, SST
         *      2: R 平方, RSQ
         *      3: 均方误差, MSE
         *      4: 调整后的 R 平方, adjRSQ
         *
         * */
//        double b = results.getParameterEstimate(0);
//        double k = results.getParameterEstimate(1);
        double b = regression.getIntercept();
        double k = regression.getSlope();
        double r2 = result.getRSquared();
        // 总离差平方和
        double tss = result.getTotalSumSquares();

//            System.out.println(tss);

        expMap.put("b", BigDecimal.valueOf(b));
        expMap.put("k", BigDecimal.valueOf(k));
        expMap.put("r2", BigDecimal.valueOf(r2));


        return expMap;
    }

    /**
     * 是否递归到全部符合或数据已达到最低点数
     *
     * @param threshold
     * @param recursion
     * @return
     */
    public Map<String, BigDecimal> offDeviatePoint(double threshold, boolean recursion) {
        if (!recursion) {
            return offDeviatePoint(threshold);
        }
        double[][] points = deviatePoint(threshold);
        if (points == null) {
            return linearFit();
        }

        SimpleRegression offRegression = new SimpleRegression();
        offRegression.addData(data);

        int count = data.length - points.length;
        RegressionResults result = null;

        List<double[]> leaveData = new ArrayList<>();

        for (double[] doubles : data) {
            boolean leave = true;
            for (double[] point : points) {
                if (doubles[0] == point[0] && doubles[1] == point[1]) {
                    leave = false;
                    break;
                }
            }
            if (leave) {
                leaveData.add(doubles);
            }


        }


        boolean isOff = false;

        while (count > 3 && points != null) {

            offRegression.removeData(points);
            result = offRegression.regress();
            points = deviatePointRecursion(offRegression, result, threshold, leaveData);
            isOff = true;
            if (points != null) {
                count = count - points.length;
            }
        }

        if (!isOff) {
            return linearFit();
        }

        if (isLinear(result)) {
            return resultToMap(offRegression, result);
        }
        throw new RuntimeException("没有线性关系");
    }


    private double[][] deviatePointRecursion(SimpleRegression offRegression, RegressionResults result, double threshold, List<double[]> leaveDatas) {

        if (isFit()) {
            List<double[]> fitData = new ArrayList<>();
            Map<Integer, double[]> fitMap = new HashMap<>();
            double b = offRegression.getIntercept();
            double k = offRegression.getSlope();
            double r2 = result.getRSquared();
            //重新计算生成拟合曲线

            int index = 0;
            for (double[] datem : leaveDatas) {
                double[] xy = {datem[1], k * datem[0] + b};
                fitMap.put(index++, xy);
//                fitData.add(xy);
            }

            List<Integer> indexList = new ArrayList<>();

            for (Map.Entry<Integer, double[]> entry : fitMap.entrySet()) {

                double[] value = entry.getValue();
                // 原始值
                BigDecimal origin = BigDecimal.valueOf(value[0]);
                // 推测值
                BigDecimal infer = BigDecimal.valueOf(value[1]);

                if (infer.compareTo(origin.multiply(BigDecimal.valueOf(1 + threshold))) > 0
                        || infer.compareTo(origin.multiply(BigDecimal.valueOf(1 - threshold))) < 0) {
                    indexList.add(entry.getKey());
                }
            }

            if (indexList.size() > 0) {
                double[][] deviatePoints = new double[indexList.size()][2];
                for (int i = indexList.size() - 1; i >= 0; i--) {
                    double[] clone = leaveDatas.get(indexList.get(i)).clone();
                    deviatePoints[i] = clone;
                    leaveDatas.remove(i);
                    LOGGER.debug("去除点位{},{}", clone[0], clone[1]);
                }

                return deviatePoints;
            }


        }
        return null;
    }
}
